using System;
using System.Windows.Forms;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using System.IO;

using System.Drawing;

using Athena.Objects;
using Athena.Core;
using Athena.Plugins;

using System.ComponentModel; 

namespace Athena
{
    public class Model
    {
        /// <summary>
        /// The size of this model window
        /// </summary>
        private SizeF _oModelDimensions = new SizeF(6000F,6000F);

        /// <summary>
        /// Whether or not new species will also have degradation reactions added to them
        /// </summary>
        bool _AutoDegradation = true;

        /// <summary>
        /// Whether or not new species will also have degradation reactions added to them
        /// </summary>
        [Category("Model"), Description("Whether or not to automatically include species degradation reactions")]
        public bool AutoDegradation
        {
            get { return _AutoDegradation; }
            set { _AutoDegradation = value; }
        }

        /// <summary>
        /// Whether or not new genes will also have species added to them
        /// </summary>
        bool _AutoGeneProduct = true;

        /// <summary>
        /// Whether or not new genes will also have species added to them
        /// </summary>
        [Category("Model"), Description("Whether or not to automatically include gene products")]
        public bool AutoGeneProduct
        {
            get { return _AutoGeneProduct; }
            set { _AutoGeneProduct = value; }
        }


        /// <summary>
        /// Default constructor initializes Drawables and BackEndModel
        /// </summary>
        public Model()
        {
            _Drawables = new List<IDrawable>();
            BackEndModel = new Athena.Core.Module("model","Untitled");            
        }

        /// <summary>
        /// The size of the model window
        /// </summary>
        [Category("Model"), Description("The dimensions of this model, this represents the maximum extend that will be drawn on the canvas")]
        public SizeF ModelDimensions
        {
            get { return _oModelDimensions; }
            set { _oModelDimensions = value; }
        }

        /// <summary>
        /// Background color, gradient, etc.
        /// </summary>
        private ModelBackground _oBackground = new ModelBackground();

        /// <summary>
        /// Background color, gradient, etc.
        /// </summary>
        [TypeConverter(typeof(BackgroundConverter)), Description("The Background for teh current model")]
        public ModelBackground Background
        {
            get { return _oBackground; }
            set { _oBackground = value; }
        }

        /// <summary>
        /// The rectangle containing all drawable objects in this model
        /// </summary>
        [Category("Model"), TypeConverter(typeof(Objects.RectangleFConverter)), Description("The bounds of all elements in this model")]
        public RectangleF Bounds
        {
            get
            {
                return Utility.GetBounds(_Drawables);
            }
        }

        /// <summary>
        /// The rectangle containing all selected objects
        /// </summary>
        [Category("Model"), TypeConverter(typeof(Objects.RectangleFConverter)), Description("The bounds of the selected elements in this model")]
        public RectangleF SelectedBounds
        {
            get
            {
                return Utility.GetBounds(_SelectedItems);
            }
        }

        /// <summary>
        /// List of all the drawable objects in this model
        /// </summary>
        private List<IDrawable> _Drawables = new List<IDrawable>();

        /// <summary>
        /// list of drawables that respond when hit by another drawable
        /// </summary>
        private List<IDrawable> _Hittables = new List<IDrawable>();

        /// <summary>
        /// This is a pointer to the back-end model, which is used to generate the simulatable model
        /// </summary>
        public Athena.Core.Module BackEndModel = new Athena.Core.Module("module","Main");    //the underlying model

        /// <summary>
        /// Informs all the drawables whether it was "hit" by a drawable
        /// </summary>
        /// <param name="drawables">drawable that should be checked</param>
        public void InformCollision(IDrawable id)
        {
            List<IDrawable> hit = new List<IDrawable>();
            /*List<IDrawable> candidates;

            if (id.RespondsToCollision)
                candidates = _Drawables;
            else
                candidates = _Hittables;
            */
            foreach (IDrawable d in _Drawables)
            {
                if (d.IsVisible && d.Bounds.IntersectsWith(id.Bounds))
                    hit.Add(d);
            }

            hit.Remove(id);
            id.HitBy(hit,false);
            foreach (IDrawable item in hit)
            {
                item.HitBy(id, false);
                //item.HitBy(id.LinkedDrawables, false);
            }
            if (id.Item != null && id.Item.Parent == null)
            {
                BackEndModel.AddItem(id.Item);
            }
        }

        /// <summary>
        /// Informs all the drawables if it has been "hit" by another drawable
        /// generally called after moving a set of drawables
        /// </summary>
        /// <param name="drawables">the drawables that should be checked for "hit"</param>
        public void InformCollision(List<IDrawable> oDrawables)
        {
            List<IDrawable> drawables = new List<IDrawable>();
            drawables.AddRange(oDrawables);
            foreach (IDrawable id in oDrawables)
            {
                drawables.AddRange(id.AnchoredItems);
            }
            foreach (IDrawable id in drawables)
            {
                List<IDrawable> hit = new List<IDrawable>();
                /*
                List<IDrawable> candidates;

                if (id.RespondsToCollision)
                    candidates = _Drawables;
                else
                    candidates = _Hittables;
                */
                foreach (IDrawable d in _Drawables)
                {
                    if (d.IsVisible && d.Bounds.IntersectsWith(id.Bounds))
                        hit.Add(d);
                }
                hit.Remove(id);
                //hit.AddRange(id.LinkedDrawables);
                id.HitBy(hit, false);
                foreach (IDrawable item in hit)
                {
                    item.HitBy(id, false);
                }
                if (id.Item != null && id.Item.Parent == null)
                {
                    BackEndModel.AddItem(id.Item);
                }
            }
        }

        public void InformMotion(List<IDrawable> drawables)
        {
            foreach (IDrawable id in drawables)
            {
                foreach (IDrawable id2 in _Hittables)
                {
                    if (id2.Bounds.IntersectsWith(id.Bounds))
                        id2.HitBy(id, true);
                }
            }
        }

        public void InformMotion(IDrawable id)
        {
            foreach (IDrawable id2 in _Hittables)
            {
                if (id2.Bounds.IntersectsWith(id.Bounds))
                    id2.HitBy(id, true);
            }

        }

        /// <summary>
        /// Find the IDrawable that has the given ID
        /// </summary>
        /// <param name="ID">ID of the drawable</param>
        /// <returns>Drawable with that ID</returns>
        public IDrawable FindItemWithID(string ID)
        {
            foreach (IDrawable id in Drawables)
            {
                if (id.Name.Equals(ID)) return id;
            }
            return null;
        }

        /// <summary>
        /// Find the Item or parameter value with the given ID
        /// </summary>
        /// <param name="sId">ID of the item</param>
        /// <returns>item with that ID</returns>
        public object this[string sId]
        {
            get
            {
                return BackEndModel[sId];
            }
            set
            {
                if (value is IDrawable)
                    BackEndModel[sId] = ((IDrawable)value).Item;
                else
                    BackEndModel[sId] = value;

                OnModelChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Find all the corresponding IDrawables that have the given list of IDs
        /// </summary>
        /// <param name="IDs"></param>
        /// <returns></returns>
        public List<IDrawable> FindItemsWithIDs(List<string> IDs)
        {
            List<IDrawable> list = new List<IDrawable>();
            foreach (IDrawable id in Drawables)
            {
                if (IDs.Contains(id.Name)) list.Add(id);
            }
            return list;
        }

        /// <summary>
        /// List of all the drawable objects in this model
        /// </summary>
        [Category("Model"), Description("The elements in this model")]
        public List<IDrawable> Drawables
        {
            get
            {
                return _Drawables;
            }
            set
            {
                /*if (_Drawables == null) _Drawables = new List<IDrawable>();
                _Drawables.Clear();
                _Hittables.Clear();
                BackEndModel = new Athena.Core.Module();                
                foreach (IDrawable id in value)
                {
                    AddToModel(id,false,false,false,false);
                }
                OnModelChanged(EventArgs.Empty);*/
                _Drawables = value;
                _Hittables.Clear();
                foreach (IDrawable id in _Drawables)
                {
                    if (id.RespondsToCollision)
                        _Hittables.Add(id);
                    if (id.Name == Empty.Name)
                    {
                        id.Original = Empty;
                    }
                }
            }
        }

        /// <summary>
        /// list of drawables that respond when hit by another 
        /// </summary>
        public List<IDrawable> Hittables
        {
            get { return _Hittables; }
            set { _Hittables = value; }
        }

        /// <summary>
        /// Make all things that are visible invisible or invisible visible
        /// </summary>
        [Category("Model"), Description("Make all Items visible")]
        public bool Visible
        {
            get
            {
                foreach (IDrawable id in _Drawables) 
                    if (!id.IsVisible) return false;

                return true;
            }
            set
            {
                if (value)
                {
                    foreach (IDrawable id in _Drawables)
                        if (!id.IsVisible) id.IsVisible = true;
                }
                else
                {
                    foreach (IDrawable id in _Drawables)
                        if (id.IsVisible) id.IsVisible = false;
                }

            }
        }

        /// <summary>
        /// Add a new drawable to the model with degredation if it is a floating species
        /// </summary>
        /// <param name="oDrawable">the new drawable</param>
        public void AddToModel(IDrawable oDrawable)
        {
            if (oDrawable != null)
                AddToModel(oDrawable, oDrawable.Item is Species && !((Species)oDrawable.Item).Boundary, oDrawable is GeneGlyph && oDrawable.Item is Part, true, true);            
        }

        /// <summary>
        /// Add a new drawable to the model
        /// </summary>
        /// <param name="oDrawable">the drawable</param>
        /// <param name="includeDegradation">whether or not to add a degradation reaction with it</param>
        /// <param name="informCollision">whether or not to apply collision events upon adding new item</param>
        /// <param name="bIssueUpdate">whether or not to issue a model update event</param>
        public void AddToModel(IDrawable oDrawable, bool includeDegradation, bool includeGeneProduct, bool informCollision, bool bIssueUpdate)
        {
            if (oDrawable == null) return;

            if (oDrawable.NullItem || BackEndModel.Contains(oDrawable.Item) || BackEndModel.AddItem(oDrawable.Item))
            {
                if (!_Drawables.Contains(oDrawable))
                {
                    _Drawables.Add(oDrawable);

                    if (oDrawable.RespondsToCollision)
                        _Hittables.Add(oDrawable);

                    if (AutoDegradation && includeDegradation && oDrawable is SpeciesGlyph)
                    {
                        AddDegradationReaction((SpeciesGlyph)oDrawable);
                    }

                    if (AutoGeneProduct && includeGeneProduct && oDrawable is GeneGlyph)
                    {
                        AddGeneProductReaction((GeneGlyph)oDrawable);
                    }
                }
            }

            if (oDrawable.Alias != null && oDrawable.Alias.Count > 0)
            {
                foreach (IDrawable alias in oDrawable.Alias)
                {
                    if (!_Drawables.Contains(alias))
                        _Drawables.Add(alias);
                }
            }

            if (oDrawable.Name == Empty.Name)
                Empty.AddAlias(oDrawable);

            if (oDrawable is Objects.ContainerGlyph)
            {
                ((Objects.ContainerGlyph)oDrawable).AddDrawablesToModel(this);
            }

            if (informCollision) InformCollision(oDrawable);

            if (bIssueUpdate) OnModelChanged(EventArgs.Empty);
        }

        /// <summary>
        /// add the degradation reaction to the given drawable
        /// </summary>
        /// <param name="oDrawable"></param>
        public void AddDegradationReaction(SpeciesGlyph oDrawable)
        {
            if (Empty is SpeciesGlyph)
            {
                SpeciesGlyph wasteGlyph = Empty.GenerateAlias() as SpeciesGlyph;

                if (wasteGlyph == null) return;

                wasteGlyph.Location = (new PointF(oDrawable.Bounds.Right + 30, oDrawable.Location.Y - 45));

                BezierConnector degrade = new BezierConnector(oDrawable, wasteGlyph);
                degrade.StraightLines = true;
                _Drawables.Add(wasteGlyph);
                _Drawables.Add(degrade);
                if (degrade.Item is Edge)
                {
                    if (oDrawable.Item != null && oDrawable.Item.Parent != null)
                        degrade.Item.Parent = oDrawable.Item.Parent;
                    else
                        degrade.Item.Parent = BackEndModel;
                    //BackEndModel.AssignParametersToReaction((Edge)degrade.Item);
                }

                wasteGlyph.AnchoredSource = oDrawable;
                degrade.AnchoredSource = oDrawable;
                InformCollision(degrade);
            }
        }

        /// <summary>
        /// add the gene product to the given drawable
        /// </summary>
        /// <param name="oDrawable"></param>
        public void AddGeneProductReaction(GeneGlyph oDrawable)
        {
            SpeciesGlyph speciesGlyph = new SpeciesGlyph(new PointF(oDrawable.Location.X + oDrawable.Dimensions.Width/4,oDrawable.Location.Y - 2*oDrawable.Dimensions.Height));
            speciesGlyph.Dimensions = new SizeF(oDrawable.Dimensions.Width/2,oDrawable.Dimensions.Height/2);

            if (speciesGlyph == null) return;

            BezierConnector transcription = new BezierConnector(oDrawable, speciesGlyph);
            transcription.StraightLines = true;
            _Drawables.Add(speciesGlyph);
            _Drawables.Add(transcription);
            
            speciesGlyph.Parent = oDrawable.Parent;
            transcription.Parent = oDrawable.Parent;

            if (oDrawable.Item != null && speciesGlyph.Item != null)
            {
                speciesGlyph.Item.Parent = oDrawable.Item.Parent;
                if (transcription.Item is Edge)
                {
                    transcription.Item.Parent = oDrawable.Item.Parent;
                    //BackEndModel.AssignParametersToReaction((Edge)transcription.Item);
                }
            }
            InformCollision(speciesGlyph);

            if (AutoDegradation)
                AddDegradationReaction(speciesGlyph);

            speciesGlyph.AnchoredSource = oDrawable;
            transcription.AnchoredSource = oDrawable;
            
        }

        /// <summary>
        /// Add a new drawable to the model with or without degredation
        /// </summary>
        /// <param name="oDrawable"></param>
        public void AddToModel(IDrawable oDrawable, bool automaticReactions)
        {
            AddToModel(oDrawable, automaticReactions, automaticReactions, true, true);
        }

        /// <summary>
        /// Checks whether this model contains this drawable (including back-end, if one exists)
        /// </summary>
        /// <param name="drawable"></param>
        /// <returns></returns>
        public bool Contains(IDrawable drawable)
        {
            return _Drawables.Contains(drawable) &&
                (drawable.Item == null || BackEndModel == null || BackEndModel.Contains(drawable.Item));
        }

        /// <summary>
        /// Add a list of drawables to the model
        /// </summary>
        /// <param name="oDrawables"></param>
        public void AddRange(List<IDrawable> oDrawables)
        {
            foreach (IDrawable oDrawable in oDrawables)
            {
                if (oDrawable.Item == null || BackEndModel.AddItem(oDrawable.Item))
                {    
                     _Drawables.Add(oDrawable);                    
                }
            }
            OnModelChanged(EventArgs.Empty);
        }

        /// <summary>
        /// Remove a Drawable from the model
        /// </summary>
        /// <param name="oDrawable"></param>
        public void RemoveDrawable(IDrawable oDrawable)
        {
            if (!_Drawables.Contains(oDrawable)) return;

            _Drawables.Remove(oDrawable);
            if (_Hittables.Contains(oDrawable))
                _Hittables.Remove(oDrawable);
            //oDrawable.Model = null;
            if (!oDrawable.NullItem)
                BackEndModel.RemoveItem(oDrawable.Item);

            oDrawable.Remove();

            OnModelChanged(EventArgs.Empty);
        }

        /// <summary>
        /// Remove all drawables in this model
        /// </summary>
        public void Clear(bool fireEvent)
        {
            for (int i = 0; i < _Drawables.Count; ++i)
            {
                _Drawables[i] = null;  //force deletion
            }
            BackEndModel.Remove();
            BackEndModel.ID = "Untitled";
            _Drawables.Clear();
            _Hittables.Clear();
            _SelectedItems.Clear();
            Module.ClearGlobals();
            
            if (fireEvent)
                OnModelChanged(EventArgs.Empty);
        }

        /// <summary>
        /// Get all the parameter names for the parameters in this model
        /// </summary>
        public void SetParameter(string name, double value)
        {
            BackEndModel.SetParameter(name, value);
        }
        /// <summary>
        /// Get all the parameter names (doubles) for this model
        /// This function uses the GlobalParameters method in Module class
        /// </summary>
        public List<string> ParameterNames
        {
            get 
            {
                List<string> names = new List<string>();
                Hashtable parameters = BackEndModel.GlobalParameters;
                if (parameters != null)
                    foreach (string s in parameters.Keys)
                        names.Add(s);
                return names;
            }
        }
        /// <summary>
        /// Get all the parameter values (doubles) for this model
        /// This function uses the GlobalParameters method in Module class
        /// </summary>
        public List<double> ParameterValues
        {
            get 
            {
                List<double> values = new List<double>();
                Hashtable parameters = BackEndModel.GlobalParameters;
                if (parameters != null)
                    foreach (string s in parameters.Keys)
                    {
                        if (parameters[s] is Core.Parameter)
                            values.Add(((Core.Parameter)parameters[s]).Value);
                    }
                return values;
            }
        }

        /// <summary>
        /// A list of all the objects that are selected
        /// </summary>
        private List<IDrawable> _SelectedItems = new List<IDrawable>();

        /// <summary>
        /// A list of all the objects that are selected
        /// </summary>
        [Category("Model"), Description("The selected elements of this model")]
        public List<IDrawable> Selected
        {
            set
            {
                if (SelectionIsDifferent(value))
                {
                    DeselectItems(_SelectedItems,false);
                    SelectItems(value);
                    OnSelectionChanged(new SelectionEventArgs(value));
                }
            }
            get
            {
                return _SelectedItems;
            }
        }

        /// <summary>
        /// Checks whether the given list is the same as the selected objects
        /// </summary>
        /// <param name="oSelected">list of drawables</param>
        /// <returns></returns>
        public bool SelectionIsDifferent(List<IDrawable> oSelected)
        {
            if (oSelected.Count != _SelectedItems.Count) return false;
            for (int i = 0; i < oSelected.Count; i++)
            {
                if (oSelected[i] != _SelectedItems[i])
                    return false;
            }
            return true;
        }

        public class SelectionEventArgs : EventArgs
        {
            public SelectionEventArgs(List<IDrawable> oSelected)
                : base()
            {
                _oSelected = oSelected;
            }
            private List<IDrawable> _oSelected;

            public List<IDrawable> SelectedObjects
            {
                get { return _oSelected; }
                set { _oSelected = value; }
            }

        }
        
        /// <summary>
        /// Invoke the Changed event; called whenever list changes:
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnSelectionChanged(SelectionEventArgs e)
        {
            if (SelectionChanged != null)
            {
                SelectionChanged(this, e);
            }
        }

        /// <summary>
        /// Responds to change in model
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnModelChanged(EventArgs e)
        {
            if (ModelChanged != null)
                ModelChanged(this, e);
        }


        public delegate void SelectionChangedDelegate(object sender, SelectionEventArgs e);

        public delegate void ModelChangedDelegate(object sender, EventArgs e);

        public event SelectionChangedDelegate SelectionChanged;

        public event ModelChangedDelegate ModelChanged;


        internal static long lTimeFinding = 0;

        /// <summary>
        /// Find all objects in the given region
        /// </summary>
        /// <param name="region">rectangle</param>
        /// <returns>list of objects in the rectangle</returns>
        public List<IDrawable> FindItems(RectangleF region)
        {
            long nTemp = DateTime.Now.Ticks;
            List<IDrawable> result = new List<IDrawable>();
            if (_Drawables == null) return result;
            foreach (IDrawable d in _Drawables)
            {
                if (d.IsVisible && d.IntersectsWith(region))
                    result.Add(d);
                //d.Where(region);
            }

            lTimeFinding += DateTime.Now.Ticks - nTemp;
            return result;
        }

        /// <summary>
        /// Find all objects at the given point
        /// </summary>
        /// <param name="location">pointf</param>
        /// <returns></returns>
        public List<IDrawable> FindItems(PointF location)
        {
            long nTemp = DateTime.Now.Ticks;
            List<IDrawable> result = new List<IDrawable>();
            if (_Drawables == null) return result;
            foreach (IDrawable d in _Drawables)
            {
                if (d.IsVisible && d.Contains(location))
                    result.Add(d);
            }
            lTimeFinding += DateTime.Now.Ticks - nTemp;
            return result;
        }

        /// <summary>
        /// Deselect all selected items
        /// </summary>
        public void DeselectItems()
        {
            _SelectedItems.Clear();
            DeselectItems(_Drawables,true);
        }

        /// <summary>
        /// Deselect all selected items
        /// </summary>
        public void DeselectItems(bool selectionEvent)
        {
            _SelectedItems.Clear();
            DeselectItems(_Drawables, selectionEvent);
        }

        /// <summary>
        /// Select all items
        /// </summary>
        public void SelectAllItems()
        {
            SelectItems(_Drawables);
        }

        /// <summary>
        /// Select the given object
        /// </summary>
        /// <param name="item">drawable</param>
        public void SelectItem(IDrawable item)
        {
            SelectItem(item, true);
        }

        /// <summary>
        /// Select the given object
        /// </summary>
        /// <param name="item">drawable</param>
        /// <param name="bSendEvent">trigger selection changed event</param>
        public void SelectItem(IDrawable item, bool bSendEvent)
        {
            item.Select();
            if (!_SelectedItems.Contains(item))
                 _SelectedItems.Add(item);
            
            if (bSendEvent) OnSelectionChanged(new SelectionEventArgs(_SelectedItems));
        }

        /// <summary>
        /// Obtain the list of types for all selected objects
        /// </summary>
        [Category("Model"), Description("The types that are currently selected")]
        public List<Type> SelectedTypes
        {
            get
            {
                List<Type> oResult = new List<Type>();
                List<IDrawable> _oSelected = Selected;
                foreach (IDrawable drawable in _oSelected)
                {
                    if (!oResult.Contains(drawable.GetType()))
                        oResult.Add(drawable.GetType());
                }
                return oResult;
            }
        }

        /// <summary>
        /// deselect all items of the given type
        /// </summary>
        /// <param name="oType"></param>
        public void DeselectItemsByType(Type oType, bool selectionEvent)
        {
            IDrawable[] oSelected = Selected.ToArray();
            foreach (IDrawable d in oSelected)
            {
                if (d.GetType() == oType)
                    DeselectItem(d, false);
            }
            if (selectionEvent) OnSelectionChanged(new SelectionEventArgs(Selected));
        }

        /// <summary>
        /// deselect the given list of items
        /// </summary>
        /// <param name="items">list of drawables</param>
        public void DeselectItems(List<IDrawable> items, bool selectionEvent)
        {
            if (items.Count == 0) return;
            foreach (IDrawable d in items)
            {
                DeselectItem(d, false);
            }
            if (selectionEvent) OnSelectionChanged(new SelectionEventArgs(Selected));
        }

        /// <summary>
        /// Deselect everything except the given list of drawables
        /// </summary>
        /// <param name="items"></param>
        public void DeselectItemsNotIn(List<IDrawable> items, bool selectEvent)
        {
            if (items.Count == 0) return;
            for (int i = 0; i < _SelectedItems.Count; ++i)
            {
                if (!items.Contains(_SelectedItems[i]))
                    DeselectItem(_SelectedItems[i], false);
            }
            if (selectEvent) OnSelectionChanged(new SelectionEventArgs(Selected));
        }

        /// <summary>
        /// Deselect the given object
        /// </summary>
        /// <param name="item"></param>
        public void DeselectItem(IDrawable item)
        {
            DeselectItem(item, true);
        }

        /// <summary>
        /// Deselect the given object
        /// </summary>
        /// <param name="item"></param>
        /// <param name="bSendEvent"></param>
        public void DeselectItem(IDrawable item, bool bSendEvent)
        {
            item.Deselect();
            if (_SelectedItems.Contains(item))
                _SelectedItems.Remove(item);
            if (bSendEvent) OnSelectionChanged(new SelectionEventArgs(Selected));
        }

        /// <summary>
        /// Deselect the selected items, and select the un-selected items in the given list
        /// </summary>
        /// <param name="items"></param>
        public void ToggleSelection(List<IDrawable> items, bool selectionEvent)
        {
            if (items.Count == 0) return;
            foreach (IDrawable d in items)
            {
                if (d.IsSelected)
                    DeselectItem(d, false);
                else
                    SelectItem(d, false);
            }
            if (selectionEvent)
                OnSelectionChanged(new SelectionEventArgs(Selected));
        }

        /// <summary>
        /// Select all the items in this given list
        /// </summary>
        /// <param name="items"></param>
        public void SelectItems(List<IDrawable> items)
        {
            try
            {
                if (items.Count == 0) return;
                IDrawable[] oList = new IDrawable[items.Count];
                items.CopyTo(oList);
                foreach (IDrawable d in oList)
                {
                    SelectItem(d, false);
                }
                OnSelectionChanged(new SelectionEventArgs(Selected));
            }
            catch
            {
            }
        }
        
        /// <summary>
        /// Open the model contained in the given file
        /// File types accepted are:  .xml, .jan, .txt, .veb
        /// </summary>
        /// <param name="sFileName">name of file</param>
        public void OpenModel(string sFileName)
        {
            DeselectItems(false);
            int numDrawables = _Drawables.Count;
            DefaultFileName = "";

            HistoryManager.History.SetControllPoint("New file opened");

            FileInfo oInfo = new FileInfo(sFileName);

            Model newModel = GetModel(sFileName);
            if (newModel == null || newModel.BackEndModel == null)
            {
                Utility.SetMessage("Error loading file");
                return;
            }

            string text = "Do you want to clear the current model?" 
                          + Environment.NewLine +
                          "(selecting no will load the file as a module)";

            if (newModel.BackEndModel.LocalItems.Count == 1 &&
                newModel.BackEndModel.LocalItems[0].Drawable != null)  //file is a single module
            {
                AddToModel(newModel.BackEndModel.LocalItems[0].Drawable, false, false, false, false);
            }
            else
            {
                if (numDrawables > 0)
                {
                    DialogResult dialogResult = MessageBox.Show(text, "Load new Model", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

                    if (dialogResult == DialogResult.Cancel)
                        return;

                    if (dialogResult == DialogResult.Yes)
                    {
                        Clear(false);
                        newModel.Clear(false);
                        newModel = GetModel(sFileName);
                        BackEndModel = newModel.BackEndModel;
                        Drawables = newModel.Drawables;
                        BackEndModel.ID = oInfo.Name.Remove(oInfo.Name.Length - oInfo.Extension.Length, oInfo.Extension.Length);
                        //DefaultFileName = sFileName;
                        Program.Form.Text = "Athena - [" + oInfo.Name + "]";
                    }
                    else
                    {
                        Module module = newModel.BackEndModel;
                        module.GenerateDrawable(Utility.DrawPanel.Location);
                        module.ID = oInfo.Name.Remove(oInfo.Name.Length - oInfo.Extension.Length, oInfo.Extension.Length);
                        AddToModel(module.Drawable, false, false, false, false);
                    }
                }
                else
                {
                    BackEndModel = newModel.BackEndModel;
                    Drawables = newModel.Drawables;
                    BackEndModel.ID = oInfo.Name.Remove(oInfo.Name.Length - oInfo.Extension.Length, oInfo.Extension.Length);
                    //DefaultFileName = sFileName;
                    Program.Form.Text = "Athena - [" + oInfo.Name + "]";
                }
            }
            OnModelChanged(EventArgs.Empty);
            Utility.Refresh();
            Utility.SetMessage("Loaded module from: " + oInfo.Name);
        }

        /// <summary>
        /// calls GetModule(string,module) in another thread to prevent freezes
        /// </summary>
        /// <param name="sFileName">file name</param>
        /// <returns></returns>
        public Model GetModel(string sFileName)
        {
            _StaticModel = null;
            Thread thread = new Thread(new ParameterizedThreadStart(LoadStaticModel));
            thread.Start(sFileName);
            thread.Join(10000);
            return _StaticModel;
        }

        static Model _StaticModel;
        /// <summary>
        /// Opens a module from Jarnac files, SBML, xml, or text files
        /// Use non static GetModule for a safer loading using threading
        /// </summary>
        /// <param name="sFileName">file containing model</param>
        /// <returns>the module with drawables in _Module</returns>
        static void LoadStaticModel(object oFileName)
        {
            string sFileName;
            if (oFileName is string)
                sFileName = (string)oFileName;
            else
                return;

            Module module = new Module();
            try
            {
                FileStream oStream = new FileStream(sFileName, FileMode.Open);

                try
                {
                    FileInfo oInfo = new FileInfo(sFileName);
                    switch (oInfo.Extension.ToUpper())
                    {
                        case ".JAN":
                        case "JAN":
                            {
                                //Clear(false);
                                StreamReader oReader = new StreamReader(oStream);
                                string sSBML = SBW.JarnacTranslator.translateSBML( 
                                     oReader.ReadToEnd() 
                                    );
                                sSBML = SBW.SBMLSupport.addMissingModifiers(sSBML);
                                oReader.Close();
                                _StaticModel = IO.SBMLReader.Instance.Read(sSBML);
                                
                                //DeselectItems();
                                //OnModelChanged(EventArgs.Empty);
                                //Utility.Refresh();
                                break;
                            }
                        case ".XML":
                        case "XML":
                            {
                                //Clear(false);
                                StreamReader oReader = new StreamReader(oStream);
                                string sSBML = oReader.ReadToEnd();
                                oReader.Close();
                                _StaticModel = IO.SBMLReader.Instance.Read(sSBML);
                                
                                //DeselectItems();
                                //OnModelChanged(EventArgs.Empty);
                                //Utility.Refresh();
                            }
                            break;
                        case ".TXT":
                        case "TXT":
                            {
                                _StaticModel = new Model();
                                List<Item> items = IO.ModuleReaderWriter.ReadModule(new StreamReader(oStream), null);
                                if (items.Count == 1 && items[0] is Module)
                                    _StaticModel.BackEndModel = items[0] as Module;
                                else
                                {
                                    foreach (Item i in items)
                                    {
                                        if (i.Drawable == null)
                                            i.GenerateDrawable(new PointF(100, 100));
                                        //_LoadingModel.AddItem(i);
                                        _StaticModel.AddToModel(i.Drawable, false, false, false, false);
                                    }
                                }
                                //OnModelChanged(EventArgs.Empty);
                                //Utility.Refresh();
                            }
                            break;
                        case ".VEB":
                        case "VEB":
                            {
                                _StaticModel = new Model();
                                _StaticModel.OpenModel(oStream);
                                break;
                            }
                        default:

                            _StaticModel = IO.XMLReader.ReadModel(oStream);
                            
                            break;
                    }
                }
                finally
                {
                    if (oStream != null)
                    {
                        oStream.Close();
                        oStream.Dispose();
                    }
                }
            }
            catch (Exception ex)
            {
                Utility.ShowError("Error while reading the model",
                    String.Format("The Model could not be saved due to an error while trying to read from the given file.{0}{1}", Environment.NewLine, ex.Message));
            }
        }

        /// <summary>
        /// Open a model from the 
        /// </summary>
        /// <param name="oStream"></param>
        public void OpenModel(Stream oStream)
        {
            //DeselectItems(false);
            Clear(false);
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            SavedModel model = formatter.Deserialize(oStream) as SavedModel;
            if (model != null)
            {
                BackEndModel = model.module;
                Drawables = model.drawables;
                DeselectItems(true);
                Utility.Refresh();
            }
        }

        /// <summary>
        /// Save a model as .xml, .jan, .txt, or .veb
        /// </summary>
        /// <param name="sFileName">file to save as</param>
        public void SaveModel(String sFileName)
        {
            try
            {
                FileInfo oInfo = new FileInfo(sFileName);
                DefaultFileName = "";
                switch (oInfo.Extension.ToUpper())
                {
                    case ".XML":
                    case "XML":
                        {
                            FileStream oStream = new FileStream(sFileName, FileMode.Create);
                            IO.SBMLWriter oWriter = new Athena.IO.SBMLWriter(oStream);
                            oWriter.WriteModel(this);
                            oStream.Close();
                            oStream.Dispose();
                        }
                        break;
                    case ".JAN":
                    case "JAN":
                        {
                            FileStream oStream = new FileStream(sFileName, FileMode.Create);
                            IO.JarnacWriter oWriter = new Athena.IO.JarnacWriter(oStream);
                            oWriter.WriteModel(this);
                            oStream.Close();
                            oStream.Dispose();
                        }
                        break;
                    case ".TXT":
                    case "TXT":
                        {
                            //if (_SelectedItems.Count > 0 && _SelectedItems[0].Item is Module)
                            {
                                StreamWriter oStream = new StreamWriter(new FileStream(sFileName, FileMode.Create));
                                try
                                {
                                    IO.ModuleReaderWriter.WriteModule(BackEndModel, oStream);
                                } 
                                catch (Exception) {}
                                oStream.Close();
                                oStream.Dispose();
                            }
                        }
                        break;
                    case ".VEB":
                    case "VEB":
                        {
                            FileStream oStream = new FileStream(sFileName, FileMode.Create);
                            SaveModel(oStream);
                            oStream.Close();
                            oStream.Dispose();
                        }
                        break;
                    default:
                        {
                            FileStream oStream = new FileStream(sFileName, FileMode.Create);
                            try
                            {
                                IO.XMLWriter.WriteModel(oStream, this, false);
                            }
                            catch (Exception) { }
                            finally
                            {
                                oStream.Close();
                                oStream.Dispose();
                            }
                            DefaultFileName = sFileName;
                            Program.Form.Text = "Athena : " + sFileName; 
                        }
                        break;
                }
                Utility.SetMessage(String.Format("File saved in {0}", sFileName));
            }
            catch (Exception ex)
            {
                Utility.ShowError("Error while saving the model",
                String.Format("The Model could not be saved due to an error while trying to read from the given file.{0}{1}", Environment.NewLine, ex.Message));
            }
        }

        /// <summary>
        /// Save this model as a binary file
        /// </summary>
        /// <param name="oStream"></param>
        public void SaveModel(Stream oStream)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            formatter.Serialize(oStream, new SavedModel(_Drawables,BackEndModel));
        }

        [Serializable()]
        private class SavedModel
        {
            public List<IDrawable> drawables;
            public Module module;
            public SavedModel(List<IDrawable> listOfDrawables, Module backEndModel)
            {
                drawables = listOfDrawables;
                module = backEndModel;
            }
        }

        /// <summary>
        /// Removes all drawables that are selected (stored in the Selected list)
        /// </summary>
        public void DeleteSelected()
        {
            HistoryManager.History.SetControllPoint("Delete Selected");
            IDrawable[] toDelete = Selected.ToArray();
            DeselectItems(false);
            //foreach (IDrawable d in toDelete)
            for (int i=0; i < toDelete.Length; ++i)
            {
                IDrawable oDrawable = toDelete[i];

                _Drawables.Remove(oDrawable);
                if (_Hittables.Contains(oDrawable))
                    _Hittables.Remove(oDrawable);
                
                if (oDrawable.Item != null)
                    BackEndModel.RemoveItem(oDrawable.Item);

                oDrawable.Remove();
            }
            OnModelChanged(EventArgs.Empty);
            Utility.Refresh();
        }

        public static IDrawable Empty
        {
            get
            {
                return Core.Module.Empty.Drawable;
            }
        }

        public string DefaultFileName = "";

    }
}
